/*!
 * \file mdump.cpp
 * \project WonderTrader
 *
 * \author VeryCD Dev Team (原始代码)
 * \date 2009-12-25
 * 
 * \brief Windows平台内存转储工具实现文件
 * 
 * \details 本文件实现了CMiniDumper类的所有功能，提供完整的程序崩溃内存转储解决方案：
 *          - 实现异常捕获和内存转储生成
 *          - 集成Windows调试帮助库(DBGHELP.DLL)
 *          - 提供转储文件的自动命名和保存
 *          - 支持崩溃报告工具的自动调用
 *          - 处理各种异常情况和错误信息显示
 *          - 为程序调试和问题诊断提供关键数据
 * 
 * \note 该实现依赖Windows特定的API和调试库
 * \warning 需要确保目标系统安装了合适版本的DBGHELP.DLL
 */
#include "mdump.h"
#include <dbghelp.h>
#include <ShellAPI.h>
#include <tchar.h>
#include <stdio.h>

// 获取数组元素个数的宏定义
#define ARRSIZE(x)	(sizeof(x)/sizeof(x[0]))

// MiniDumpWriteDump函数指针类型定义
typedef BOOL(WINAPI *MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType,
	CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
	CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
	CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);

// 全局内存转储器实例
CMiniDumper theCrashDumper;

// 静态成员变量定义
TCHAR CMiniDumper::m_szAppName[MAX_PATH] = { 0 };	// 应用程序名称
TCHAR CMiniDumper::m_szDumpPath[MAX_PATH] = { 0 };	// 转储文件路径

/*!
 * \brief 启用内存转储功能
 * \param pszAppName 应用程序名称，用于转储文件命名
 * \param bShowErrors 是否显示错误信息对话框
 * \param pszDumpPath 转储文件保存路径，默认为空（使用程序目录）
 * 
 * \details 初始化内存转储器并设置异常过滤器：
 *          - 复制应用程序名称和转储路径到静态变量
 *          - 加载Windows调试帮助库
 *          - 获取MiniDumpWriteDump函数指针
 *          - 注册顶级异常过滤器
 *          - 释放调试库句柄
 */
void CMiniDumper::Enable(LPCTSTR pszAppName, bool bShowErrors, LPCTSTR pszDumpPath/* = ""*/)
{
	// 如果此断言触发，说明有两个CMiniDumper实例，这是不允许的
	_tcsncpy(m_szAppName, pszAppName, ARRSIZE(m_szAppName));
	_tcsncpy(m_szDumpPath, pszDumpPath, ARRSIZE(m_szDumpPath));

	MINIDUMPWRITEDUMP pfnMiniDumpWriteDump = NULL;
	HMODULE hDbgHelpDll = GetDebugHelperDll((FARPROC*)&pfnMiniDumpWriteDump, bShowErrors);
	if (hDbgHelpDll)
	{
		// 如果成功获取MiniDumpWriteDump函数，则设置异常过滤器
		if (pfnMiniDumpWriteDump)
			SetUnhandledExceptionFilter(TopLevelFilter);
		
		// 释放调试库句柄
		FreeLibrary(hDbgHelpDll);
		hDbgHelpDll = NULL;
		pfnMiniDumpWriteDump = NULL;
	}
}

/*!
 * \brief 获取调试帮助库句柄
 * \param ppfnMiniDumpWriteDump 返回MiniDumpWriteDump函数指针
 * \param bShowErrors 是否显示错误信息
 * \return 调试帮助库句柄，失败返回NULL
 * 
 * \details 加载DBGHELP.DLL并获取MiniDumpWriteDump函数：
 *          - 尝试加载DBGHELP.DLL库
 *          - 检查库是否加载成功
 *          - 获取MiniDumpWriteDump函数地址
 *          - 验证函数版本兼容性
 *          - 根据需要显示错误信息
 */
HMODULE CMiniDumper::GetDebugHelperDll(FARPROC* ppfnMiniDumpWriteDump, bool bShowErrors)
{
	*ppfnMiniDumpWriteDump = NULL;
	
	// 尝试加载调试帮助库
	HMODULE hDll = LoadLibrary(_T("DBGHELP.DLL"));
	if (hDll == NULL)
	{
		// 库加载失败，显示错误信息
		if (bShowErrors) {
			// 不要本地化此字符串（实际上，不要使用MFC加载它）！
			MessageBox(NULL, _T("DBGHELP.DLL not found. Please install a DBGHELP.DLL."), m_szAppName, MB_ICONSTOP | MB_OK);
		}
	}
	else
	{
		// 获取MiniDumpWriteDump函数地址
		*ppfnMiniDumpWriteDump = GetProcAddress(hDll, "MiniDumpWriteDump");
		if (*ppfnMiniDumpWriteDump == NULL)
		{
			// 函数不存在或版本过旧
			if (bShowErrors) {
				// 不要本地化此字符串（实际上，不要使用MFC加载它）！
				MessageBox(NULL, _T("DBGHELP.DLL found is too old. Please upgrade to a newer version of DBGHELP.DLL."), m_szAppName, MB_ICONSTOP | MB_OK);
			}
		}
	}
	return hDll;
}

/*!
 * \brief 顶级异常过滤器
 * \param pExceptionInfo 异常信息指针
 * \return 异常处理结果代码
 * 
 * \details 处理未捕获的异常并生成内存转储：
 *          - 初始化返回值为继续搜索
 *          - 获取调试帮助库和转储函数
 *          - 构建转储文件路径和名称
 *          - 创建转储文件并写入异常信息
 *          - 调用外部崩溃报告工具
 *          - 根据编译模式决定是否终止进程
 */
LONG CMiniDumper::TopLevelFilter(struct _EXCEPTION_POINTERS* pExceptionInfo)
{
	LONG lRetValue = EXCEPTION_CONTINUE_SEARCH;
	TCHAR szResult[_MAX_PATH + 1024] = { 0 };
	MINIDUMPWRITEDUMP pfnMiniDumpWriteDump = NULL;
	HMODULE hDll = GetDebugHelperDll((FARPROC*)&pfnMiniDumpWriteDump, true);
	HINSTANCE	hInstCrashReporter = NULL;	// 由fengwen于2006/11/15添加：使用新的崩溃错误报告工具

	if (hDll)
	{
		if (pfnMiniDumpWriteDump)
		{
			//MessageBox(NULL,"test","test",MB_OK);
			// 询问用户是否要保存转储文件
			// 不要本地化此字符串（实际上，不要使用MFC加载它）！
			// 由fengwen于2006/11/15注释掉 <开始>：使用新的崩溃错误报告工具
			//if (MessageBox(NULL, _T("eMule crashed :-(\r\n\r\nA diagnostic file can be created which will help the author to resolve this problem. This file will be saved on your Disk (and not sent).\r\n\r\nDo you want to create this file now?"), m_szAppName, MB_ICONSTOP | MB_YESNO) == IDYES)
			// 由fengwen于2006/11/15注释掉 <结束>：使用新的崩溃错误报告工具
			{
				// 创建转储文件的完整路径
				TCHAR szDumpPath[_MAX_PATH] = { 0 };
				if(_tcsclen(m_szDumpPath) == 0)
				{
					// 如果未指定转储路径，使用程序所在目录
					GetModuleFileName(NULL, szDumpPath, ARRSIZE(szDumpPath));
					LPTSTR pszFileName = _tcsrchr(szDumpPath, _T('\\'));
					if (pszFileName) {
						pszFileName++;
						*pszFileName = _T('\0');
					}
				}
				else
				{
					// 使用指定的转储路径
					_tcsncpy(szDumpPath, m_szDumpPath, _tcsclen(m_szDumpPath));
					szDumpPath[_tcsclen(m_szDumpPath)] = _T('\0');
				}

				// 替换文件名中的空格和点号
				TCHAR szBaseName[_MAX_PATH] = { 0 };
				_tcsncat(szBaseName, m_szAppName, ARRSIZE(szBaseName) - 1);
				LPTSTR psz = szBaseName;
				while (*psz != _T('\0')) {
					if (*psz == _T('.'))
						*psz = _T('-');		// 将点号替换为连字符
					else if (*psz == _T(' '))
						*psz = _T('_');		// 将空格替换为下划线
					psz++;
				}
				_tcsncat(szDumpPath, szBaseName, ARRSIZE(szDumpPath) - 1);
				
				// 添加时间戳到文件名
				SYSTEMTIME curTime;
				GetLocalTime(&curTime);
				char buf[64];
				sprintf(buf, "%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d", curTime.wYear, curTime.wMonth, curTime.wDay, curTime.wHour, curTime.wMinute, curTime.wSecond);
				strcat(szDumpPath, buf);

				// 添加.dmp扩展名
				_tcsncat(szDumpPath, _T(".dmp"), ARRSIZE(szDumpPath) - 1);

				// 创建转储文件
				HANDLE hFile = CreateFile(szDumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
				if (hFile != INVALID_HANDLE_VALUE)
				{
					// 设置异常信息结构
					_MINIDUMP_EXCEPTION_INFORMATION ExInfo = { 0 };
					ExInfo.ThreadId = GetCurrentThreadId();
					ExInfo.ExceptionPointers = pExceptionInfo;
					ExInfo.ClientPointers = NULL;

					// 写入内存转储
					BOOL bOK = (*pfnMiniDumpWriteDump)(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &ExInfo, NULL, NULL);
					if (bOK)
					{
						// 转储成功
						// 不要本地化此字符串（实际上，不要使用MFC加载它）！
						_sntprintf(szResult, ARRSIZE(szResult), _T("Saved dump file to \"%s\".\r\n\r\nPlease send this file together with a detailed bug report to bastet.wang@gmail.com !\r\n\r\nThank you for helping to improve Tsts."), szDumpPath);
						lRetValue = EXCEPTION_EXECUTE_HANDLER;

						// 由fengwen于2006/11/15添加 <开始>：使用新的崩溃错误报告工具
						hInstCrashReporter = ShellExecute(NULL, _T("open"), _T("CrashReporter.exe"), szDumpPath, NULL, SW_SHOW);
						if (hInstCrashReporter <= (HINSTANCE)32)
							lRetValue = EXCEPTION_CONTINUE_SEARCH;
						// 由fengwen于2006/11/15添加 <结束>：使用新的崩溃错误报告工具
					}
					else
					{
						// 转储失败
						// 不要本地化此字符串（实际上，不要使用MFC加载它）！
						_sntprintf(szResult, ARRSIZE(szResult), _T("Failed to save dump file to \"%s\".\r\n\r\nError: %u"), szDumpPath, GetLastError());
					}
					CloseHandle(hFile);
				}
				else
				{
					// 文件创建失败
					// 不要本地化此字符串（实际上，不要使用MFC加载它）！
					_sntprintf(szResult, ARRSIZE(szResult), _T("Failed to create dump file \"%s\".\r\n\r\nError: %u"), szDumpPath, GetLastError());
				}
			}
		}
		// 释放调试库
		FreeLibrary(hDll);
		hDll = NULL;
		pfnMiniDumpWriteDump = NULL;
	}

	// 由fengwen于2006/11/15注释掉 <开始>：使用新的崩溃错误报告工具
	//if (szResult[0] != _T('\0'))
	//	MessageBox(NULL, szResult, m_szAppName, MB_ICONINFORMATION | MB_OK);
	// 由fengwen于2006/11/15注释掉 <结束>：使用新的崩溃错误报告工具

#ifndef _DEBUG
	// 在Release模式下的处理
	if (EXCEPTION_EXECUTE_HANDLER == lRetValue)		// 由fengwen于2006/11/15添加：可从filter处理异常，然后终止进程
	{
		// 仅在Release版本中退出进程，以便在Debug版本中将异常传递给可能安装的调试器
		ExitProcess(0);
	}
	else
		return lRetValue;

#else
	// Debug模式下直接返回，让调试器处理异常
	return lRetValue;
#endif
}
